import { ActiveEdge, AEL, Intersection } from "./ael";
import { Path } from "./geometry";
import { PriorityQueue } from "./priority_queue";
import { LocalMin } from "./localmin";
import { VertexFlag, Vertex } from "./vertex";

export enum PathType {
  SUBJECT = 0,
  CLIP = 1,
}

export class Clipper {
  dirty: boolean = true;
  currLMLIndex: number = 0;
  localMinList: LocalMin[] = [];
  scanYQ: PriorityQueue = new PriorityQueue((a, b) =>
    a > b ? -1 : a === b ? 0 : 1
  );
  ael: AEL = new AEL();

  constructor() {}

  addSubject(path: Path, properties: any = undefined) {
    this.addPaths([path], PathType.SUBJECT, false, properties);
  }

  addClip(path: Path, properties: any = undefined) {
    this.addPaths([path], PathType.CLIP, false, properties);
  }

  execute() {
    this.reset();
    let y = this.popScanLine();
    while (y) {
      this.insertLocalMinimumsToAEL(y);
      y = this.popScanLine();
      if (!y) {
        break;
      }
      this.doIntersections(y);
      this.doTopOfScanbeam(y);
    }
    return true;
  }

  addPaths(
    paths: Path[],
    type: PathType,
    isOpen: boolean = false,
    properties: any
  ) {
    for (const path of paths) {
      let v0: Vertex | undefined,
        v: Vertex | undefined,
        prev_v: Vertex | undefined;

      for (const p of path.points) {
        if (!prev_v) {
          v0 = new Vertex(p);
          prev_v = v0;
          continue;
        }

        // 去除重复点
        if (!p.equals(prev_v.point)) {
          v = new Vertex(p);
          v.prev = prev_v;
          prev_v.next = v;
          prev_v = v;
        }
      }

      // 至少包含两个不同的点
      if (!v0 || !v) {
        continue;
      }

      // 抹去首尾重复的点
      if (!isOpen && v0.point.equals(v.point)) {
        v = v.prev;
      }

      // 首尾相连形成环形链表
      v!.next = v0;
      v0.prev = v;

      // 计算LocalMinimum
      this.caculateLocalMinimum(v0, type, isOpen, properties);
    }
  }

  caculateLocalMinimum(
    v0: Vertex,
    type: PathType,
    isOpen: boolean,
    properties: any
  ) {
    let goingUp0: boolean = false;
    let v, prev_v: Vertex;
    if (isOpen) {
      v = v0.next;
      while (v !== v0 && v!.y === v0.y) {
        v = v!.next;
      }

      goingUp0 = v!.y < v0.y;
      if (goingUp0) {
        v0.flag = VertexFlag.OPEN_START | VertexFlag.LOCAL_MIN;
        this.pushLocalMinimum(new LocalMin(v0, type, properties));
      } else {
        v0.flag = VertexFlag.OPEN_START | VertexFlag.LOCAL_MAX;
      }
    } else {
      v = v0.prev;
      while (v !== v0 && v!.y === v0.y) {
        v = v!.prev;
      }

      if (v === v0) {
        console.log(`一条平线!!!`);
        return;
      }

      goingUp0 = v!.y > v0.y;
    }

    let goingUp: boolean = goingUp0;
    prev_v = v0;
    v = v0.next;
    while (v !== v0) {
      if (v!.y > prev_v.y && goingUp) {
        prev_v.flag |= VertexFlag.LOCAL_MAX;
        goingUp = false;
      } else if (v!.y < prev_v.y && !goingUp) {
        prev_v.flag |= VertexFlag.LOCAL_MIN;
        this.pushLocalMinimum(new LocalMin(prev_v, type, properties));
        goingUp = true;
      }

      prev_v = v!;
      v = v!.next;
    }

    if (isOpen) {
      prev_v.flag = VertexFlag.OPEN_END;
      if (!goingUp) {
        prev_v.flag |= VertexFlag.LOCAL_MIN;
        this.pushLocalMinimum(new LocalMin(prev_v, type, properties));
      } else {
        prev_v.flag |= VertexFlag.LOCAL_MAX;
      }
    } else if (goingUp !== goingUp0) {
      if (goingUp) {
        prev_v.flag |= VertexFlag.LOCAL_MAX;
      } else {
        prev_v.flag |= VertexFlag.LOCAL_MIN;
        this.pushLocalMinimum(new LocalMin(prev_v, type, properties));
      }
    }
  }

  reset() {
    if (this.dirty) {
      this.localMinList.sort(LocalMin.compare);
      this.dirty = false;
    }
    this.currLMLIndex = 0;
    this.ael.reset();
  }

  pushLocalMinimum(lm: LocalMin) {
    this.dirty = true;
    this.localMinList.push(lm);
    this.pushScanLine(lm.v.y);
  }

  insertLocalMinimumsToAEL(y: number) {
    while (this.currLMLIndex < this.localMinList.length) {
      let localMinmum = this.localMinList[this.currLMLIndex];
      if (localMinmum.v.y !== y) {
        break;
      }

      this.__insertLocalMinimumToAEL(localMinmum, y);
      this.currLMLIndex++;
    }
  }

  __insertLocalMinimumToAEL(lm: LocalMin, y: number) {
    let leftBound, rightBound: ActiveEdge | undefined;
    if ((lm.v.flag | VertexFlag.OPEN_START) !== VertexFlag.OPEN_START) {
      leftBound = new ActiveEdge(lm.type, lm.v, lm.v.prev!, lm);
      leftBound.win_dx = -1;
    }

    if ((lm.v.flag | VertexFlag.OPEN_END) !== VertexFlag.OPEN_END) {
      rightBound = new ActiveEdge(lm.type, lm.v, lm.v.next!, lm);
      rightBound.win_dx = 1;
    }

    if (leftBound && rightBound && leftBound.slope < rightBound.slope) {
      [leftBound, rightBound] = [rightBound, leftBound];
    }

    if (!leftBound) {
      leftBound = rightBound;
      rightBound = undefined;
    }

    this.ael.insert(leftBound!);
    this.pushScanLine(leftBound!.top.y);

    if (rightBound) {
      this.ael.insertAfterInAEL(rightBound, leftBound!);

      // 计算rightbound正确位置并交互
      let e = rightBound;
      while (
        e.next_in_ael &&
        ActiveEdge.compare(e.next_in_ael, rightBound) < 0
      ) {
        e = e.next_in_ael;
      }
      if (e !== rightBound) {
        this.ael.swapInAEL(rightBound, e);
      }

      this.pushScanLine(rightBound.top.y);
    }
  }

  pushScanLine(y: number) {
    this.scanYQ.push(y);
  }

  popScanLine() {
    let y = this.scanYQ.pop();
    if (y) {
      while (this.scanYQ.top === y) {
        this.scanYQ.pop();
      }
    }
    return y;
  }

  doIntersections(y: number) {
    let intss = this.ael.buildIntersections(y);
    intss.sort(Intersection.compare);
    for (const ints of intss) {
      console.log(`ints at ${ints.point}`);
      if (ints.edge1.next_in_ael === ints.edge2) {
        this.ael.swapInAEL(ints.edge1, ints.edge2);
      }
    }
  }
  doTopOfScanbeam(y: number) {
    let edge = this.ael.aelRoot;
    while (edge) {
      if (edge.top.y === y) {
        if ((edge.top.flag & VertexFlag.LOCAL_MAX) === VertexFlag.LOCAL_MAX) {
          this.ael.removeEdge(edge);
        } else {
          edge.bottom = edge.top;
          edge.top = edge.nextVertex!;
          edge.updateSlope();
          this.pushScanLine(edge.top.y);
        }
      }
      edge = edge.next_in_ael;
    }
  }
}
